import * as Cesium from 'cesium';
import { PassGaussianSplatting } from './fixPassGaussianSplatting';
const { Scene, defined, SceneMode, MapMode2D, Cartesian3, BoundingRectangle, Cartographic, Math: CesiumMath,
    //@ts-ignore
    Matrix4, Transforms, DebugCameraPrimitive, Pass, ClearCommand, EllipsoidGeometry, Camera, defaultValue,
    PerspectiveFrustum, PerspectiveOffCenterFrustum, OrthographicFrustum, OrthographicOffCenterFrustum, mergeSort,
    PerInstanceColorAppearance, BoxGeometry, Primitive, GeometryInstance, GeometryPipeline, ColorGeometryInstanceAttribute,
    //@ts-ignore
    SunPostProcess, DerivedCommand, Color
} = Cesium;
const scratch2DViewport = new BoundingRectangle();
const scratch2DViewportSavedPosition = new Cartesian3();
const scratch2DViewportCartographic = new Cartographic(
    Math.PI,
    CesiumMath.PI_OVER_TWO,
);
const scratch2DViewportMaxCoord = new Cartesian3();
const scratch2DViewportCameraTransform = new Matrix4();
const scratch2DViewportTransform = new Matrix4();
const scratch2DViewportEyePoint = new Cartesian3();
const scratch2DViewportWindowCoords = new Cartesian3();
export default function fixScene() {
    //@ts-ignore
    Scene.prototype.updateAndExecuteCommands = function (
        passState: any,
        backgroundColor: any,
    ) {
        updateAndClearFramebuffers(this, passState, backgroundColor);
        //@ts-ignore
        if (this._environmentState.useWebVR) {
            executeWebVRCommands(this, passState);
        } else if (
            //@ts-ignore
            this._frameState.mode !== SceneMode.SCENE2D ||
            //@ts-ignore
            this._mapMode2D === MapMode2D.ROTATE
        ) {
            executeCommandsInViewport(true, this, passState);
        } else {
            execute2DViewportCommands(this, passState);
        }
    };
    function execute2DViewportCommands(scene: any, passState: any) {
        const { frameState, camera } = scene;
        const { uniformState } = scene.context;

        const originalViewport = passState.viewport;
        const viewport = BoundingRectangle.clone(originalViewport, scratch2DViewport);
        passState.viewport = viewport;

        const maxCartographic = scratch2DViewportCartographic;
        const maxCoord = scratch2DViewportMaxCoord;

        const projection = scene.mapProjection;
        projection.project(maxCartographic, maxCoord);

        const position = Cartesian3.clone(
            camera.position,
            scratch2DViewportSavedPosition,
        );
        const transform = Matrix4.clone(
            camera.transform,
            scratch2DViewportCameraTransform,
        );
        const frustum = camera.frustum.clone();

        camera._setTransform(Matrix4.IDENTITY);

        const viewportTransformation = Matrix4.computeViewportTransformation(
            viewport,
            0.0,
            1.0,
            scratch2DViewportTransform,
        );
        const projectionMatrix = camera.frustum.projectionMatrix;

        const x = camera.positionWC.y;
        const eyePoint = Cartesian3.fromElements(
            CesiumMath.sign(x) * maxCoord.x - x,
            0.0,
            -camera.positionWC.x,
            scratch2DViewportEyePoint,
        );
        //@ts-ignore
        const windowCoordinates = Transforms.pointToGLWindowCoordinates(
            projectionMatrix,
            viewportTransformation,
            eyePoint,
            scratch2DViewportWindowCoords,
        );

        windowCoordinates.x = Math.floor(windowCoordinates.x);

        const viewportX = viewport.x;
        const viewportWidth = viewport.width;

        if (
            x === 0.0 ||
            windowCoordinates.x <= viewportX ||
            windowCoordinates.x >= viewportX + viewportWidth
        ) {
            executeCommandsInViewport(true, scene, passState);
        } else if (
            Math.abs(viewportX + viewportWidth * 0.5 - windowCoordinates.x) < 1.0
        ) {
            viewport.width = windowCoordinates.x - viewport.x;

            camera.position.x *= CesiumMath.sign(camera.position.x);

            camera.frustum.right = 0.0;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(
                camera.positionWC,
                camera.directionWC,
                camera.upWC,
            );
            uniformState.update(frameState);

            executeCommandsInViewport(true, scene, passState);

            viewport.x = windowCoordinates.x;

            camera.position.x = -camera.position.x;

            camera.frustum.right = -camera.frustum.left;
            camera.frustum.left = 0.0;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(
                camera.positionWC,
                camera.directionWC,
                camera.upWC,
            );
            uniformState.update(frameState);

            executeCommandsInViewport(false, scene, passState);
        } else if (windowCoordinates.x > viewportX + viewportWidth * 0.5) {
            viewport.width = windowCoordinates.x - viewportX;

            const right = camera.frustum.right;
            camera.frustum.right = maxCoord.x - x;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(
                camera.positionWC,
                camera.directionWC,
                camera.upWC,
            );
            uniformState.update(frameState);

            executeCommandsInViewport(true, scene, passState);

            viewport.x = windowCoordinates.x;
            viewport.width = viewportX + viewportWidth - windowCoordinates.x;

            camera.position.x = -camera.position.x;

            camera.frustum.left = -camera.frustum.right;
            camera.frustum.right = right - camera.frustum.right * 2.0;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(
                camera.positionWC,
                camera.directionWC,
                camera.upWC,
            );
            uniformState.update(frameState);

            executeCommandsInViewport(false, scene, passState);
        } else {
            viewport.x = windowCoordinates.x;
            viewport.width = viewportX + viewportWidth - windowCoordinates.x;

            const left = camera.frustum.left;
            camera.frustum.left = -maxCoord.x - x;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(
                camera.positionWC,
                camera.directionWC,
                camera.upWC,
            );
            uniformState.update(frameState);

            executeCommandsInViewport(true, scene, passState);

            viewport.x = viewportX;
            viewport.width = windowCoordinates.x - viewportX;

            camera.position.x = -camera.position.x;

            camera.frustum.right = -camera.frustum.left;
            camera.frustum.left = left - camera.frustum.left * 2.0;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(
                camera.positionWC,
                camera.directionWC,
                camera.upWC,
            );
            uniformState.update(frameState);

            executeCommandsInViewport(false, scene, passState);
        }

        camera._setTransform(transform);
        Cartesian3.clone(position, camera.position);
        camera.frustum = frustum.clone();
        passState.viewport = originalViewport;
    }
    function executeCommandsInViewport(firstViewport: any, scene: any, passState: any) {
        const view = scene._view;
        const { renderTranslucentDepthForPick } = scene._environmentState;

        if (!firstViewport) {
            scene.frameState.commandList.length = 0;
        }

        updateAndRenderPrimitives(scene);

        view.createPotentiallyVisibleSet(scene);

        if (firstViewport) {
            executeComputeCommands(scene);
            if (!renderTranslucentDepthForPick) {
                executeShadowMapCastCommands(scene);
            }
        }

        executeCommands(scene, passState);
    }
    const scratchEyeTranslation = new Cartesian3();
    function executeWebVRCommands(scene: any, passState: any) {
        const view = scene._view;
        const camera = view.camera;
        const environmentState = scene._environmentState;
        const renderTranslucentDepthForPick =
            environmentState.renderTranslucentDepthForPick;

        updateAndRenderPrimitives(scene);

        view.createPotentiallyVisibleSet(scene);

        executeComputeCommands(scene);

        if (!renderTranslucentDepthForPick) {
            executeShadowMapCastCommands(scene);
        }

        // Based on Calculating Stereo pairs by Paul Bourke
        // http://paulbourke.net/stereographics/stereorender/
        const viewport = passState.viewport;
        viewport.x = 0;
        viewport.y = 0;
        viewport.width = viewport.width * 0.5;
        //@ts-ignore
        const savedCamera = Camera.clone(camera, scene._cameraVR);
        savedCamera.frustum = camera.frustum;

        const near = camera.frustum.near;
        const fo = near * defaultValue(scene.focalLength, 5.0);
        const eyeSeparation = defaultValue(scene.eyeSeparation, fo / 30.0);
        const eyeTranslation = Cartesian3.multiplyByScalar(
            savedCamera.right,
            eyeSeparation * 0.5,
            scratchEyeTranslation,
        );

        camera.frustum.aspectRatio = viewport.width / viewport.height;

        const offset = (0.5 * eyeSeparation * near) / fo;

        Cartesian3.add(savedCamera.position, eyeTranslation, camera.position);
        camera.frustum.xOffset = offset;

        executeCommands(scene, passState);

        viewport.x = viewport.width;

        Cartesian3.subtract(savedCamera.position, eyeTranslation, camera.position);
        camera.frustum.xOffset = -offset;

        executeCommands(scene, passState);
        //@ts-ignore
        Camera.clone(savedCamera, camera);
    }
    function executeCommands(scene: any, passState: any) {
        const { camera, context, frameState } = scene;
        const { uniformState } = context;

        uniformState.updateCamera(camera);

        const frustum = createWorkingFrustum(camera);
        frustum.near = camera.frustum.near;
        frustum.far = camera.frustum.far;

        const passes = frameState.passes;
        const picking = passes.pick || passes.pickVoxel;

        // Ideally, we would render the sky box and atmosphere last for
        // early-z, but we would have to draw it in each frustum.
        // Do not render environment primitives during a pick pass since they do not generate picking commands.
        if (!picking) {
            renderEnvironment(scene, passState);
        }

        const {
            clearGlobeDepth,
            renderTranslucentDepthForPick,
            useDepthPlane,
            useGlobeDepthFramebuffer,
            useInvertClassification,
            usePostProcessSelected,
        } = scene._environmentState;

        const {
            globeDepth,
            globeTranslucencyFramebuffer,
            sceneFramebuffer,
            frustumCommandsList,
        } = scene._view;
        const numFrustums = frustumCommandsList.length;

        const globeTranslucencyState = scene._globeTranslucencyState;
        const clearDepth = scene._depthClearCommand;
        const clearStencil = scene._stencilClearCommand;
        const clearClassificationStencil = scene._classificationStencilClearCommand;
        const depthPlane = scene._depthPlane;

        const height2D = camera.position.z;

        function performPass(frustumCommands: any, passId: any) {
            uniformState.updatePass(passId);
            const commands = frustumCommands.commands[passId];
            const commandCount = frustumCommands.indices[passId];
            for (let j = 0; j < commandCount; ++j) {
                executeCommand(commands[j], scene, passState);
            }
            return commandCount;
        }

        function performIdPass(frustumCommands: any, passId: any) {
            uniformState.updatePass(passId);
            const commands = frustumCommands.commands[passId];
            const commandCount = frustumCommands.indices[passId];
            for (let j = 0; j < commandCount; ++j) {
                executeIdCommand(commands[j], scene, passState);
            }
        }

        // Execute commands in each frustum in back to front order
        for (let i = 0; i < numFrustums; ++i) {
            const index = numFrustums - i - 1;
            const frustumCommands = frustumCommandsList[index];

            if (scene.mode === SceneMode.SCENE2D) {
                // To avoid z-fighting in 2D, move the camera to just before the frustum
                // and scale the frustum depth to be in [1.0, nearToFarDistance2D].
                camera.position.z = height2D - frustumCommands.near + 1.0;
                frustum.far = Math.max(1.0, frustumCommands.far - frustumCommands.near);
                frustum.near = 1.0;
                uniformState.update(frameState);
                uniformState.updateFrustum(frustum);
            } else {
                // Avoid tearing artifacts between adjacent frustums in the opaque passes
                frustum.near =
                    index !== 0
                        ? frustumCommands.near * scene.opaqueFrustumNearOffset
                        : frustumCommands.near;
                frustum.far = frustumCommands.far;
                uniformState.updateFrustum(frustum);
            }

            clearDepth.execute(context, passState);

            if (context.stencilBuffer) {
                clearStencil.execute(context, passState);
            }

            if (globeTranslucencyState.translucent) {
                uniformState.updatePass(Pass.GLOBE);
                globeTranslucencyState.executeGlobeCommands(
                    frustumCommands,
                    executeCommand,
                    globeTranslucencyFramebuffer,
                    scene,
                    passState,
                );
            } else {
                performPass(frustumCommands, Pass.GLOBE);
            }

            if (useGlobeDepthFramebuffer) {
                globeDepth.executeCopyDepth(context, passState);
            }

            // Draw terrain classification
            if (!renderTranslucentDepthForPick) {
                if (globeTranslucencyState.translucent) {
                    uniformState.updatePass(Pass.TERRAIN_CLASSIFICATION);
                    globeTranslucencyState.executeGlobeClassificationCommands(
                        frustumCommands,
                        executeCommand,
                        globeTranslucencyFramebuffer,
                        scene,
                        passState,
                    );
                } else {
                    performPass(frustumCommands, Pass.TERRAIN_CLASSIFICATION);
                }
            }

            if (clearGlobeDepth) {
                clearDepth.execute(context, passState);
                if (useDepthPlane) {
                    depthPlane.execute(context, passState);
                }
            }

            let commandCount;
            if (!useInvertClassification || picking || renderTranslucentDepthForPick) {
                // Common/fastest path. Draw 3D Tiles and classification normally.

                // Draw 3D Tiles
                commandCount = performPass(frustumCommands, Pass.CESIUM_3D_TILE);

                if (commandCount > 0) {
                    if (useGlobeDepthFramebuffer) {
                        globeDepth.prepareColorTextures(context, clearGlobeDepth);
                        globeDepth.executeUpdateDepth(
                            context,
                            passState,
                            globeDepth.depthStencilTexture,
                        );
                    }

                    // Draw classifications. Modifies 3D Tiles color.
                    if (!renderTranslucentDepthForPick) {
                        commandCount = performPass(
                            frustumCommands,
                            Pass.CESIUM_3D_TILE_CLASSIFICATION,
                        );
                    }
                }
            } else {
                // When the invert classification color is opaque:
                //    Main FBO (FBO1):                   Main_Color   + Main_DepthStencil
                //    Invert classification FBO (FBO2) : Invert_Color + Main_DepthStencil
                //
                //    1. Clear FBO2 color to vec4(0.0) for each frustum
                //    2. Draw 3D Tiles to FBO2
                //    3. Draw classification to FBO2
                //    4. Fullscreen pass to FBO1, draw Invert_Color when:
                //           * Main_DepthStencil has the stencil bit set > 0 (classified)
                //    5. Fullscreen pass to FBO1, draw Invert_Color * czm_invertClassificationColor when:
                //           * Main_DepthStencil has stencil bit set to 0 (unclassified) and
                //           * Invert_Color !== vec4(0.0)
                //
                // When the invert classification color is translucent:
                //    Main FBO (FBO1):                  Main_Color         + Main_DepthStencil
                //    Invert classification FBO (FBO2): Invert_Color       + Invert_DepthStencil
                //    IsClassified FBO (FBO3):          IsClassified_Color + Invert_DepthStencil
                //
                //    1. Clear FBO2 and FBO3 color to vec4(0.0), stencil to 0, and depth to 1.0
                //    2. Draw 3D Tiles to FBO2
                //    3. Draw classification to FBO2
                //    4. Fullscreen pass to FBO3, draw any color when
                //           * Invert_DepthStencil has the stencil bit set > 0 (classified)
                //    5. Fullscreen pass to FBO1, draw Invert_Color when:
                //           * Invert_Color !== vec4(0.0) and
                //           * IsClassified_Color !== vec4(0.0)
                //    6. Fullscreen pass to FBO1, draw Invert_Color * czm_invertClassificationColor when:
                //           * Invert_Color !== vec4(0.0) and
                //           * IsClassified_Color === vec4(0.0)
                //
                // NOTE: Step six when translucent invert color occurs after the TRANSLUCENT pass
                //
                scene._invertClassification.clear(context, passState);

                const opaqueClassificationFramebuffer = passState.framebuffer;
                passState.framebuffer = scene._invertClassification._fbo.framebuffer;

                // Draw normally
                commandCount = performPass(frustumCommands, Pass.CESIUM_3D_TILE);

                if (useGlobeDepthFramebuffer) {
                    scene._invertClassification.prepareTextures(context);
                    globeDepth.executeUpdateDepth(
                        context,
                        passState,
                        scene._invertClassification._fbo.getDepthStencilTexture(),
                    );
                }

                // Set stencil
                commandCount = performPass(
                    frustumCommands,
                    Pass.CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW,
                );

                passState.framebuffer = opaqueClassificationFramebuffer;

                // Fullscreen pass to copy classified fragments
                scene._invertClassification.executeClassified(context, passState);
                if (frameState.invertClassificationColor.alpha === 1.0) {
                    // Fullscreen pass to copy unclassified fragments when alpha == 1.0
                    scene._invertClassification.executeUnclassified(context, passState);
                }

                // Clear stencil set by the classification for the next classification pass
                if (commandCount > 0 && context.stencilBuffer) {
                    clearClassificationStencil.execute(context, passState);
                }

                // Draw style over classification.
                commandCount = performPass(
                    frustumCommands,
                    Pass.CESIUM_3D_TILE_CLASSIFICATION,
                );
            }

            if (commandCount > 0 && context.stencilBuffer) {
                clearStencil.execute(context, passState);
            }

            performVoxelsPass(scene, passState, frustumCommands);

            performPass(frustumCommands, Pass.OPAQUE);

            performGaussianSplatPass(scene, passState, frustumCommands);

            if (index !== 0 && scene.mode !== SceneMode.SCENE2D) {
                // Do not overlap frustums in the translucent pass to avoid blending artifacts
                frustum.near = frustumCommands.near;
                uniformState.updateFrustum(frustum);
            }

            performTranslucentPass(scene, passState, frustumCommands);

            performTranslucent3DTilesClassification(scene, passState, frustumCommands);

            if (
                context.depthTexture &&
                scene.useDepthPicking &&
                (useGlobeDepthFramebuffer || renderTranslucentDepthForPick)
            ) {
                // PERFORMANCE_IDEA: Use MRT to avoid the extra copy.
                const pickDepth = scene._picking.getPickDepth(scene, index);
                pickDepth.update(context, globeDepth.depthStencilTexture);
                pickDepth.executeCopyDepth(context, passState);
            }

            if (picking || !usePostProcessSelected) {
                continue;
            }

            const originalFramebuffer = passState.framebuffer;
            passState.framebuffer = sceneFramebuffer.getIdFramebuffer();

            // reset frustum
            frustum.near =
                index !== 0
                    ? frustumCommands.near * scene.opaqueFrustumNearOffset
                    : frustumCommands.near;
            frustum.far = frustumCommands.far;
            uniformState.updateFrustum(frustum);

            if (globeTranslucencyState.translucent) {
                uniformState.updatePass(Pass.GLOBE);
                globeTranslucencyState.executeGlobeCommands(
                    frustumCommands,
                    executeIdCommand,
                    globeTranslucencyFramebuffer,
                    scene,
                    passState,
                );
            } else {
                performIdPass(frustumCommands, Pass.GLOBE);
            }

            if (clearGlobeDepth) {
                clearDepth.framebuffer = passState.framebuffer;
                clearDepth.execute(context, passState);
                clearDepth.framebuffer = undefined;
            }

            if (clearGlobeDepth && useDepthPlane) {
                depthPlane.execute(context, passState);
            }

            performIdPass(frustumCommands, Pass.CESIUM_3D_TILE);
            performIdPass(frustumCommands, Pass.OPAQUE);
            performIdPass(frustumCommands, Pass.TRANSLUCENT);

            passState.framebuffer = originalFramebuffer;
        }
    }
}
//#region 原始代码
function performTranslucent3DTilesClassification(
    scene: any,
    passState: any,
    frustumCommands: any,
) {
    const { translucentTileClassification, globeDepth } = scene._view;
    const has3DTilesClassificationCommands =
        frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION] > 0;
    if (
        !has3DTilesClassificationCommands ||
        !translucentTileClassification.isSupported()
    ) {
        return;
    }

    const commands = frustumCommands.commands[Pass.TRANSLUCENT];
    translucentTileClassification.executeTranslucentCommands(
        scene,
        executeCommand,
        passState,
        commands,
        globeDepth.depthStencilTexture,
    );
    translucentTileClassification.executeClassificationCommands(
        scene,
        executeCommand,
        passState,
        frustumCommands,
    );
}
function performTranslucentPass(scene: any, passState: any, frustumCommands: any) {
    const { frameState, context } = scene;
    const { pick, pickVoxel } = frameState.passes;
    const picking = pick || pickVoxel;

    let invertClassification;
    if (
        !picking &&
        scene._environmentState.useInvertClassification &&
        frameState.invertClassificationColor.alpha < 1.0
    ) {
        // Fullscreen pass to copy unclassified fragments when alpha < 1.0.
        // Not executed when undefined.
        invertClassification = scene._invertClassification;
    }

    const executeTranslucentCommands =
        obtainTranslucentCommandExecutionFunction(scene);

    context.uniformState.updatePass(Pass.TRANSLUCENT);
    const commands = frustumCommands.commands[Pass.TRANSLUCENT];
    commands.length = frustumCommands.indices[Pass.TRANSLUCENT];
    executeTranslucentCommands(
        scene,
        executeCommand,
        passState,
        commands,
        invertClassification,
    );
}
function obtainTranslucentCommandExecutionFunction(scene: any) {
    if (scene._environmentState.useOIT) {
        if (!defined(scene._executeOITFunction)) {
            const { view, context } = scene;
            scene._executeOITFunction = function (
                scene: any,
                executeFunction: any,
                passState: any,
                commands: any,
                invertClassification: any,
            ) {
                view.globeDepth.prepareColorTextures(context);
                view.oit.executeCommands(
                    scene,
                    executeFunction,
                    passState,
                    commands,
                    invertClassification,
                );
            };
        }
        return scene._executeOITFunction;
    }
    if (scene.frameState.passes.render) {
        return executeTranslucentCommandsBackToFront;
    }
    return executeTranslucentCommandsFrontToBack;
}
function backToFront(a: any, b: any, position: any) {
    return (
        b.boundingVolume.distanceSquaredTo(position) -
        a.boundingVolume.distanceSquaredTo(position)
    );
}
function executeTranslucentCommandsBackToFront(
    scene: any,
    executeFunction: any,
    passState: any,
    commands: any,
    invertClassification: any,
) {
    mergeSort(commands, backToFront, scene.camera.positionWC);

    if (defined(invertClassification)) {
        executeFunction(invertClassification.unclassifiedCommand, scene, passState);
    }

    for (let i = 0; i < commands.length; ++i) {
        executeFunction(commands[i], scene, passState);
    }
}
function frontToBack(a: any, b: any, position: any) {
    // When distances are equal equal favor sorting b before a. This gives render priority to commands later in the list.
    return (
        a.boundingVolume.distanceSquaredTo(position) -
        b.boundingVolume.distanceSquaredTo(position) +
        CesiumMath.EPSILON12
    );
}
function executeTranslucentCommandsFrontToBack(
    scene: any,
    executeFunction: any,
    passState: any,
    commands: any,
    invertClassification: any,
) {
    mergeSort(commands, frontToBack, scene.camera.positionWC);

    if (defined(invertClassification)) {
        executeFunction(invertClassification.unclassifiedCommand, scene, passState);
    }

    for (let i = 0; i < commands.length; ++i) {
        executeFunction(commands[i], scene, passState);
    }
}
function performVoxelsPass(scene: any, passState: any, frustumCommands: any) {
    //@ts-ignore
    scene.context.uniformState.updatePass(Pass.VOXELS);
    //@ts-ignore

    const commands = frustumCommands.commands[Pass.VOXELS];
    //@ts-ignore

    commands.length = frustumCommands.indices[Pass.VOXELS];

    mergeSort(commands, backToFront, scene.camera.positionWC);

    for (let i = 0; i < commands.length; ++i) {
        executeCommand(commands[i], scene, passState);
    }
}
const scratchCart3 = new Cartesian3();
function distanceSquaredToCenter(center: any, position: any) {
    const diff = Cartesian3.subtract(center, position, scratchCart3);
    const distance = Math.max(0.0, Cartesian3.magnitude(diff));
    return distance * distance;
}
function backToFrontSplats(a: any, b: any, position: any) {
    const boxA = a.boundingVolume;
    const boxB = b.boundingVolume;

    return (
        distanceSquaredToCenter(boxB.center, position) -
        distanceSquaredToCenter(boxA.center, position)
    );
}
function performGaussianSplatPass(scene: any, passState: any, frustumCommands: any) {
    // @ts-ignore
    scene.context.uniformState.updatePass(PassGaussianSplatting.GAUSSIAN_SPLATS);
    //@ts-ignore
    const commands = frustumCommands.commands[PassGaussianSplatting.GAUSSIAN_SPLATS];
    //@ts-ignore
    commands.length = frustumCommands.indices[PassGaussianSplatting.GAUSSIAN_SPLATS];

    //still necessary?
    mergeSort(commands, backToFrontSplats, scene.camera.positionWC);

    for (let i = 0; i < commands.length; ++i) {
        executeCommand(commands[i], scene, passState);
    }
}
function executeIdCommand(command: any, scene: any, passState: any) {
    const { derivedCommands } = command;
    if (!defined(derivedCommands)) {
        return;
    }

    const frameState = scene._frameState;
    const context = scene._context;

    if (frameState.useLogDepth && defined(derivedCommands.logDepth)) {
        command = derivedCommands.logDepth.command;
    }

    const { picking, pickingMetadata, depth } = command.derivedCommands;
    if (defined(pickingMetadata)) {
        command = derivedCommands.pickingMetadata.pickMetadataCommand;
        command.execute(context, passState);
    }
    if (defined(picking)) {
        command = picking.pickCommand;
        command.execute(context, passState);
    } else if (defined(depth)) {
        command = depth.depthOnlyCommand;
        command.execute(context, passState);
    }
}
function renderEnvironment(scene: any, passState: any) {
    const { context, environmentState, view } = scene;

    context.uniformState.updatePass(Pass.ENVIRONMENT);

    if (defined(environmentState.skyBoxCommand)) {
        executeCommand(environmentState.skyBoxCommand, scene, passState);
    }

    if (environmentState.isSkyAtmosphereVisible) {
        executeCommand(environmentState.skyAtmosphereCommand, scene, passState);
    }

    if (environmentState.isSunVisible) {
        environmentState.sunDrawCommand.execute(context, passState);
        if (scene.sunBloom && !environmentState.useWebVR) {
            let framebuffer;
            if (environmentState.useGlobeDepthFramebuffer) {
                framebuffer = view.globeDepth.framebuffer;
            } else if (environmentState.usePostProcess) {
                framebuffer = view.sceneFramebuffer.framebuffer;
            } else {
                framebuffer = environmentState.originalFramebuffer;
            }
            scene._sunPostProcess.execute(context);
            scene._sunPostProcess.copy(context, framebuffer);
            passState.framebuffer = framebuffer;
        }
    }

    // Moon can be seen through the atmosphere, since the sun is rendered after the atmosphere.
    if (environmentState.isMoonVisible) {
        environmentState.moonCommand.execute(context, passState);
    }
}
function insertShadowCastCommands(scene: any, commandList: any, shadowMap: any) {
    const { shadowMapCullingVolume, isPointLight, passes } = shadowMap;
    const numberOfPasses = passes.length;

    const shadowedPasses = [
        Pass.GLOBE,
        Pass.CESIUM_3D_TILE,
        Pass.OPAQUE,
        Pass.TRANSLUCENT,
    ];

    for (let i = 0; i < commandList.length; ++i) {
        const command = commandList[i];
        scene.updateDerivedCommands(command);

        if (
            !command.castShadows ||
            shadowedPasses.indexOf(command.pass) < 0 ||
            !scene.isVisible(shadowMapCullingVolume, command)
        ) {
            continue;
        }

        if (isPointLight) {
            for (let k = 0; k < numberOfPasses; ++k) {
                passes[k].commandList.push(command);
            }
        } else if (numberOfPasses === 1) {
            passes[0].commandList.push(command);
        } else {
            let wasVisible = false;
            // Loop over cascades from largest to smallest
            for (let j = numberOfPasses - 1; j >= 0; --j) {
                const cascadeVolume = passes[j].cullingVolume;
                if (scene.isVisible(cascadeVolume, command)) {
                    passes[j].commandList.push(command);
                    wasVisible = true;
                } else if (wasVisible) {
                    // If it was visible in the previous cascade but now isn't
                    // then there is no need to check any more cascades
                    break;
                }
            }
        }
    }
}
function executeShadowMapCastCommands(scene: any) {
    const { shadowState, commandList } = scene.frameState;
    const { shadowsEnabled, shadowMaps } = shadowState;

    if (!shadowsEnabled) {
        return;
    }

    const { context } = scene;
    const { uniformState } = context;

    for (let i = 0; i < shadowMaps.length; ++i) {
        const shadowMap = shadowMaps[i];
        if (shadowMap.outOfView) {
            continue;
        }

        // Reset the command lists
        const { passes } = shadowMap;
        for (let j = 0; j < passes.length; ++j) {
            passes[j].commandList.length = 0;
        }

        // Insert the primitive/model commands into the shadow map command lists
        insertShadowCastCommands(scene, commandList, shadowMap);

        for (let j = 0; j < passes.length; ++j) {
            const pass = shadowMap.passes[j];
            const { camera, commandList } = pass;
            uniformState.updateCamera(camera);
            shadowMap.updatePass(context, j);
            for (let k = 0; k < commandList.length; ++k) {
                const command = commandList[k];
                // Set the correct pass before rendering into the shadow map because some shaders
                // conditionally render based on whether the pass is translucent or opaque.
                uniformState.updatePass(command.pass);
                const castCommand = command.derivedCommands.shadows.castCommands[i];
                executeCommand(castCommand, scene, pass.passState);
            }
        }
    }
}
function executeCommand(command: any, scene: any, passState: any, debugFramebuffer?: any) {
    const frameState = scene._frameState;
    const context = scene._context;

    if (defined(scene.debugCommandFilter) && !scene.debugCommandFilter(command)) {
        return;
    }

    if (command instanceof ClearCommand) {
        command.execute(context, passState);
        return;
    }

    if (command.debugShowBoundingVolume && defined(command.boundingVolume)) {
        debugShowBoundingVolume(command, scene, passState, debugFramebuffer);
    }

    if (frameState.useLogDepth && defined(command.derivedCommands.logDepth)) {
        command = command.derivedCommands.logDepth.command;
    }

    const passes = frameState.passes;
    if (
        !passes.pick &&
        !passes.pickVoxel &&
        !passes.depth &&
        scene._hdr &&
        defined(command.derivedCommands) &&
        defined(command.derivedCommands.hdr)
    ) {
        command = command.derivedCommands.hdr.command;
    }

    if (passes.pick || passes.depth) {
        if (passes.pick && !passes.depth) {
            if (
                frameState.pickingMetadata &&
                defined(command.derivedCommands.pickingMetadata)
            ) {
                command = command.derivedCommands.pickingMetadata.pickMetadataCommand;
                command.execute(context, passState);
                return;
            }
            if (
                !frameState.pickingMetadata &&
                defined(command.derivedCommands.picking)
            ) {
                command = command.derivedCommands.picking.pickCommand;
                command.execute(context, passState);
                return;
            }
        } else if (defined(command.derivedCommands.depth)) {
            command = command.derivedCommands.depth.depthOnlyCommand;
            command.execute(context, passState);
            return;
        }
    }

    if (scene.debugShowCommands || scene.debugShowFrustums) {
        scene._debugInspector.executeDebugShowFrustumsCommand(
            scene,
            command,
            passState,
        );
        return;
    }

    if (
        frameState.shadowState.lightShadowsEnabled &&
        command.receiveShadows &&
        defined(command.derivedCommands.shadows)
    ) {
        // If the command receives shadows, execute the derived shadows command.
        // Some commands, such as OIT derived commands, do not have derived shadow commands themselves
        // and instead shadowing is built-in. In this case execute the command regularly below.
        command.derivedCommands.shadows.receiveCommand.execute(context, passState);
    } else {
        command.execute(context, passState);
    }
}
let transformFrom2D = new Matrix4(0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,);
function debugShowBoundingVolume(command: any, scene: any, passState: any, debugFramebuffer: any) {
    const frameState = scene._frameState;
    const context = frameState.context;
    const boundingVolume = command.boundingVolume;

    if (defined(scene._debugVolume)) {
        scene._debugVolume.destroy();
    }

    let center = Cartesian3.clone(boundingVolume.center);
    if (frameState.mode !== SceneMode.SCENE3D) {
        center = Matrix4.multiplyByPoint(transformFrom2D, center, center);
        const projection = frameState.mapProjection;
        const centerCartographic = projection.unproject(center);
        center = projection.ellipsoid.cartographicToCartesian(centerCartographic);
    }

    let geometry;
    let modelMatrix;
    const { radius } = boundingVolume;
    if (defined(radius)) {
        geometry = EllipsoidGeometry.createGeometry(
            new EllipsoidGeometry({
                radii: new Cartesian3(radius, radius, radius),
                vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT,
            }),
        );
        modelMatrix = Matrix4.fromTranslation(center);
    } else {
        geometry = BoxGeometry.createGeometry(
            BoxGeometry.fromDimensions({
                dimensions: new Cartesian3(2.0, 2.0, 2.0),
                vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT,
            }),
        );
        modelMatrix = Matrix4.fromRotationTranslation(
            boundingVolume.halfAxes,
            center,
            new Matrix4(),
        );
    }
    scene._debugVolume = new Primitive({
        geometryInstances: new GeometryInstance({
            geometry: GeometryPipeline.toWireframe(<Cesium.Geometry>geometry),
            modelMatrix: modelMatrix,
            attributes: {
                color: new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0),
            },
        }),
        appearance: new PerInstanceColorAppearance({
            flat: true,
            translucent: false,
        }),
        asynchronous: false,
    });

    const savedCommandList = frameState.commandList;
    const commandList = (frameState.commandList = []);
    scene._debugVolume.update(frameState);

    command = commandList[0];

    if (frameState.useLogDepth) {
        const logDepth = DerivedCommand.createLogDepthCommand(command, context);
        command = logDepth.command;
    }

    let framebuffer;
    if (defined(debugFramebuffer)) {
        framebuffer = passState.framebuffer;
        passState.framebuffer = debugFramebuffer;
    }

    command.execute(context, passState);

    if (defined(framebuffer)) {
        passState.framebuffer = framebuffer;
    }

    frameState.commandList = savedCommandList;
}
function executeComputeCommands(scene: any) {
    scene.context.uniformState.updatePass(Pass.COMPUTE);

    const sunComputeCommand = scene._environmentState.sunComputeCommand;
    if (defined(sunComputeCommand)) {
        sunComputeCommand.execute(scene._computeEngine);
    }

    const commandList = scene._computeCommandList;
    for (let i = 0; i < commandList.length; ++i) {
        commandList[i].execute(scene._computeEngine);
    }
}
function updateAndRenderPrimitives(scene: any) {
    const frameState = scene._frameState;

    scene._groundPrimitives.update(frameState);
    scene._primitives.update(frameState);

    updateDebugFrustumPlanes(scene);
    updateShadowMaps(scene);

    if (scene._globe) {
        scene._globe.render(frameState);
    }
}
function updateDebugFrustumPlanes(scene: any) {
    const frameState = scene._frameState;
    if (scene.debugShowFrustumPlanes !== scene._debugShowFrustumPlanes) {
        if (scene.debugShowFrustumPlanes) {
            scene._debugFrustumPlanes = new DebugCameraPrimitive({
                camera: scene.camera,
                updateOnChange: false,
                frustumSplits: frameState.frustumSplits,
            });
        } else {
            scene._debugFrustumPlanes =
                scene._debugFrustumPlanes && scene._debugFrustumPlanes.destroy();
        }
        scene._debugShowFrustumPlanes = scene.debugShowFrustumPlanes;
    }

    if (defined(scene._debugFrustumPlanes)) {
        scene._debugFrustumPlanes.update(frameState);
    }
}
function updateShadowMaps(scene: any) {
    const frameState = scene._frameState;
    const { passes, shadowState, shadowMaps } = frameState;
    const length = shadowMaps.length;

    const shadowsEnabled =
        length > 0 &&
        !passes.pick &&
        !passes.pickVoxel &&
        scene.mode === SceneMode.SCENE3D;
    if (shadowsEnabled !== shadowState.shadowsEnabled) {
        // Update derived commands when shadowsEnabled changes
        ++shadowState.lastDirtyTime;
        shadowState.shadowsEnabled = shadowsEnabled;
    }

    shadowState.lightShadowsEnabled = false;

    if (!shadowsEnabled) {
        return;
    }

    // Check if the shadow maps are different than the shadow maps last frame.
    // If so, the derived commands need to be updated.
    for (let j = 0; j < length; ++j) {
        if (shadowMaps[j] !== shadowState.shadowMaps[j]) {
            ++shadowState.lastDirtyTime;
            break;
        }
    }

    shadowState.shadowMaps.length = 0;
    shadowState.lightShadowMaps.length = 0;

    for (let i = 0; i < length; ++i) {
        const shadowMap = shadowMaps[i];
        shadowMap.update(frameState);

        shadowState.shadowMaps.push(shadowMap);

        if (shadowMap.fromLightSource) {
            shadowState.lightShadowMaps.push(shadowMap);
            shadowState.lightShadowsEnabled = true;
        }

        if (shadowMap.dirty) {
            ++shadowState.lastDirtyTime;
            shadowMap.dirty = false;
        }
    }
}

function updateAndClearFramebuffers(scene: any, passState: any, clearColor: any) {
    const context = scene._context;
    const frameState = scene._frameState;
    const environmentState = scene._environmentState;
    const view = scene._view;

    const passes = frameState.passes;
    const picking = passes.pick || passes.pickVoxel;
    if (defined(view.globeDepth)) {
        view.globeDepth.picking = picking;
    }
    const useWebVR = environmentState.useWebVR;

    // Preserve the reference to the original framebuffer.
    environmentState.originalFramebuffer = passState.framebuffer;

    // Manage sun bloom post-processing effect.
    if (defined(scene.sun) && scene.sunBloom !== scene._sunBloom) {
        if (scene.sunBloom && !useWebVR) {
            scene._sunPostProcess = new SunPostProcess();
        } else if (defined(scene._sunPostProcess)) {
            scene._sunPostProcess = scene._sunPostProcess.destroy();
        }

        scene._sunBloom = scene.sunBloom;
    } else if (!defined(scene.sun) && defined(scene._sunPostProcess)) {
        scene._sunPostProcess = scene._sunPostProcess.destroy();
        scene._sunBloom = false;
    }

    // Clear the pass state framebuffer.
    const clear = scene._clearColorCommand;
    Color.clone(clearColor, clear.color);
    clear.execute(context, passState);

    // Update globe depth rendering based on the current context and clear the globe depth framebuffer.
    // Globe depth is copied for the pick pass to support picking batched geometries in GroundPrimitives.
    const useGlobeDepthFramebuffer = (environmentState.useGlobeDepthFramebuffer =
        defined(view.globeDepth));
    if (useGlobeDepthFramebuffer) {
        view.globeDepth.update(
            context,
            passState,
            view.viewport,
            scene.msaaSamples,
            scene._hdr,
            environmentState.clearGlobeDepth,
        );
        view.globeDepth.clear(context, passState, clearColor);
    }

    // If supported, configure OIT to use the globe depth framebuffer and clear the OIT framebuffer.
    const oit = view.oit;
    const useOIT = (environmentState.useOIT =
        !picking && defined(oit) && oit.isSupported());
    if (useOIT) {
        oit.update(
            context,
            passState,
            view.globeDepth.colorFramebufferManager,
            scene._hdr,
            scene.msaaSamples,
        );
        oit.clear(context, passState, clearColor);
        environmentState.useOIT = oit.isSupported();
    }

    const postProcess = scene.postProcessStages;
    let usePostProcess = (environmentState.usePostProcess =
        !picking &&
        (scene._hdr ||
            postProcess.length > 0 ||
            postProcess.ambientOcclusion.enabled ||
            postProcess.fxaa.enabled ||
            postProcess.bloom.enabled));
    environmentState.usePostProcessSelected = false;
    if (usePostProcess) {
        view.sceneFramebuffer.update(
            context,
            view.viewport,
            scene._hdr,
            scene.msaaSamples,
        );
        view.sceneFramebuffer.clear(context, passState, clearColor);

        postProcess.update(context, frameState.useLogDepth, scene._hdr);
        postProcess.clear(context);

        usePostProcess = environmentState.usePostProcess = postProcess.ready;
        environmentState.usePostProcessSelected =
            usePostProcess && postProcess.hasSelected;
    }

    if (environmentState.isSunVisible && scene.sunBloom && !useWebVR) {
        passState.framebuffer = scene._sunPostProcess.update(passState);
        scene._sunPostProcess.clear(context, passState, clearColor);
    } else if (useGlobeDepthFramebuffer) {
        passState.framebuffer = view.globeDepth.framebuffer;
    } else if (usePostProcess) {
        passState.framebuffer = view.sceneFramebuffer.framebuffer;
    }

    if (defined(passState.framebuffer)) {
        clear.execute(context, passState);
    }

    const useInvertClassification = (environmentState.useInvertClassification =
        !picking && defined(passState.framebuffer) && scene.invertClassification);
    if (useInvertClassification) {
        let depthFramebuffer;
        if (frameState.invertClassificationColor.alpha === 1.0) {
            if (useGlobeDepthFramebuffer) {
                depthFramebuffer = view.globeDepth.framebuffer;
            }
        }

        if (defined(depthFramebuffer) || context.depthTexture) {
            scene._invertClassification.previousFramebuffer = depthFramebuffer;
            scene._invertClassification.update(
                context,
                scene.msaaSamples,
                view.globeDepth.colorFramebufferManager,
            );
            scene._invertClassification.clear(context, passState);

            if (frameState.invertClassificationColor.alpha < 1.0 && useOIT) {
                const command = scene._invertClassification.unclassifiedCommand;
                const derivedCommands = command.derivedCommands;
                derivedCommands.oit = oit.createDerivedCommands(
                    command,
                    context,
                    derivedCommands.oit,
                );
            }
        } else {
            environmentState.useInvertClassification = false;
        }
    }

    if (scene._globeTranslucencyState.translucent) {
        view.globeTranslucencyFramebuffer.updateAndClear(
            scene._hdr,
            view.viewport,
            context,
            passState,
        );
    }
}
const scratchPerspectiveFrustum = new PerspectiveFrustum();
const scratchPerspectiveOffCenterFrustum = new PerspectiveOffCenterFrustum();
const scratchOrthographicFrustum = new OrthographicFrustum();
const scratchOrthographicOffCenterFrustum = new OrthographicOffCenterFrustum();
function createWorkingFrustum(camera: any) {
    const { frustum } = camera;
    if (defined(frustum.fov)) {
        return frustum.clone(scratchPerspectiveFrustum);
    }
    if (defined(frustum.infiniteProjectionMatrix)) {
        return frustum.clone(scratchPerspectiveOffCenterFrustum);
    }
    if (defined(frustum.width)) {
        return frustum.clone(scratchOrthographicFrustum);
    }
    return frustum.clone(scratchOrthographicOffCenterFrustum);
}
//#endregion